package server;

import server.Game;
import server.Server;
import shared.CompObj;
import shared.Consts;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import network.NetQProxy;
import packet.ChatCmd;
import packet.CleanLobbyReq;
import packet.CommandRequest;
import packet.InviteToGameCmd;
import packet.LobbyJoinCmd;
import packet.PlayerQuitCmd;
import packet.Request;

/**
 * Lobby engine for InEquity
 * @author Joel Garboden
 */
public class Lobby
{
  PriorityBlockingQueue<CommandRequest> lobbyQ;
  Server server;

  Thread connectionlistener;

  public ArrayList<LobbyNode> lobbyList;
  public Lobby lobbyAccessor;

  public ArrayList<Game> gameList;

  /**
   * Constructor
   * @param lobbyQ the queue to take requests from
   * @param server the server GUI, for logging
   * @param lobbyList client list
   */
  public Lobby(PriorityBlockingQueue<CommandRequest> lobbyQ, Server server, ArrayList<LobbyNode> lobbyList)
  {
    this.lobbyQ = lobbyQ;
    this.server = server;
    lobbyAccessor = this;
    this.lobbyList = lobbyList;

    gameList = new ArrayList<>();
  }

  /**
   * Broadcasts chat messages to all clients
   * @param playerName the origin of the message (System, playerName)
   * @param message the message to send
   */
  public void broadcastChat(String playerName, String message)
  {
    ChatCmd cmd = new ChatCmd(playerName, message);
    for(LobbyNode p: lobbyList)
    {
      p.send(cmd);
    }
  }

  /**
   * Accessor
   * @return the number of chatters
   */
  public int numChatters()
  {
    return lobbyList.size();
  }

  /**
   * Adds a NetQProxy for incoming connection
   * @param proxy proxy to be added
   * @return successful completion
   */
  public boolean addPlayer(NetQProxy proxy)
  {
    LobbyNode lobbyNode = new LobbyNode(proxy);
    lobbyList.add(lobbyNode);
    return true;
  }

  /**
   * Begins game mode
   * @param lobbyClients
   */
  public void startGameCmd(ArrayList<LobbyNode> lobbyClients)
  {
    if(gameList.size() >= Consts.GAME_SPAWN_LIMIT)
    {
      System.err.println(Consts.GAME_LIMIT_REACHED);
      broadcastChat(Consts.GM_NAME, Consts.GAME_LIMIT_REACHED);
      return;
    }

    boolean validPort = false;
    int gamePort = Consts.DEFAULT_PORT;

    System.err.println("Start game cmd is under development");
    cleanLobby();

    ServerSocket testSocket = null;

    while(!validPort)
    {
      try
      {
        testSocket = new ServerSocket(gamePort);
        validPort = true;
        System.out.println("");
      }
      catch(SocketException e)
      {
        ++gamePort;
        System.out.println("Socket:Port is now: " + gamePort);
        validPort = false;
      }
      catch(IOException e)
      {
        ++gamePort;
        System.out.println("IO:Port is now: " + gamePort);
        validPort = false;
      }
    }

    final ServerSocket gameSocket = testSocket;

    final PriorityBlockingQueue<CommandRequest> gameQ =
            new PriorityBlockingQueue<>
            (Consts.SERVER_QUEUE_SIZE, new CompObj());

    final Game newGame = new Game(gameQ);

    gameList.add(newGame);
    newGame.startListener();

    for(LobbyNode node: lobbyClients)
    {
      node.send(new InviteToGameCmd(Consts.DEFAULT_IP, gamePort, gameList.size()));
    }


    Runnable rOut = new Runnable()
    {
      @Override
      public void run()
      {
        while(true)
        {
          newGame.addPlayer(server.acceptClientConnection(newGame.playerList.size(), gameSocket, gameQ));
        }
      }
    };

    Thread reconnector = new Thread(rOut);
    reconnector.start();

  }

  /**
   * Starts a LobbyListener to handle incoming packets
   */
  public void startListener()
  {
    new LobbyListener().start();
  }

  /**
   * Handles initialization of new clients joining
   * @param playerID ID of client
   * @param playerName name of joining client
   */
  public void playerJoinCmd(int playerID, String playerName)
  {
    if(playerName == null)
    {
      playerName = "Unknown";
    }

    LobbyJoinCmd joinCmd = new LobbyJoinCmd();

    lobbyList.get(playerID).setName(playerName);
    lobbyList.get(playerID).send(joinCmd);

    broadcastChat(Consts.GM_NAME, playerName + " " + Consts.JOIN_MSG);

    ChatCmd helloCmd = new ChatCmd(Consts.GM_NAME, Consts.WELCOME_MSG);
    lobbyList.get(playerID).send(helloCmd);
  }

  /**
   * Handles players quitting
   * @param playerName name of player who's quitting
   * @throws IndexOutOfBoundsException when player isn't found
   */
  public void playerQuitCmd(String playerName)
  {
    for (int i = 0; i < lobbyList.size(); ++i)
    {
      if (lobbyList.get(i).getName().equals(playerName))
      {
        lobbyList.get(i).setActive(false);
        PlayerQuitCmd cmd = new PlayerQuitCmd();
        lobbyList.get(i).send(cmd);
        broadcastChat(Consts.GM_NAME, lobbyList.get(i).getName()
                                      + " " + Consts.QUIT_MSG);
        lobbyQ.add(new CleanLobbyReq());
        return;
      }
    }

    server.postLogMsg("Player: " + playerName+ " not found!");
  }

  /**
   * Cleans inactive lobby members
   */
  public void cleanLobby()
  {
    for (int i = 0; i < lobbyList.size(); ++i)
    {
      if (!lobbyList.get(i).isActive())
      {
        lobbyList.remove(i);
      }
    }

    lobbyList.trimToSize();
  }

  /**
   * LobbyListener for handling incoming packets
   * @author Joel Garboden
   */
  public class LobbyListener extends Thread
  {
    boolean listen = true;

    /**
     * Attempts to halt the listening thread as quickly as possible
     */
    public void stopNow()
    {
      try
      {
        listen = false;
        this.interrupt();
      }
      catch (Throwable ex)
      {
        Logger.getLogger(LobbyListener.class.getName()).log(Level.SEVERE, "LobbyListener", ex);
      }
    }

    /**
     * Continually listens on incoming network stream and <br/>
     * drops packets to queue for processing
     */
    @Override
    public void run()
    {
      try
      {
        while (listen)
        {
          server.postLogMsg("Trying to take from lobbyQ");
          Request req = (Request) lobbyQ.take();
          server.postLogMsg("Received: " + req.toString());

          req.lobbyRequest(lobbyAccessor);
        }
      }
      catch (InterruptedException ex)
      {
        Logger.getLogger(LobbyListener.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
  }
}
